//
//  Dave.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 22/06/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#pragma once

#include <cstdint>

#include "ClockReceiver/ClockReceiver.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Numeric/LFSR.hpp"
#include "Outputs/Speaker/Implementation/BufferSource.hpp"

namespace Enterprise::Dave {

enum class Interrupt: uint8_t {
	VariableFrequency = 0x02,
	OneHz = 0x08,
	Nick = 0x20,
};

/*!
	Models the audio-production subset of Dave's behaviour.
*/
class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
public:
	Audio(Concurrency::AsyncTaskQueue<false> &audio_queue);

	/// Modifies an register in the audio range; only the low 4 bits are
	/// used for register decoding so it's assumed that the caller has
	/// already identified this write as being to an audio register.
	void write(uint16_t address, uint8_t value);

	// MARK: - SampleSource.
	void set_sample_volume_range(int16_t range);
	template <Outputs::Speaker::Action action>
	void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target);

private:
	Concurrency::AsyncTaskQueue<false> &audio_queue_;

	// Global divider (i.e. 8MHz/12Mhz switch).
	uint8_t global_divider_;
	uint8_t global_divider_reload_ = 2;

	// Tone channels.
	struct Channel {
		// User-set values.
		uint16_t reload = 0;
		bool high_pass = false;
		bool ring_modulate = false;
		enum class Distortion {
			None = 0,
			FourBit = 1,
			FiveBit = 2,
			SevenBit = 3,
		} distortion = Distortion::None;
		uint8_t amplitude[2]{};
		bool sync = false;

		// Current state.
		uint16_t count = 0;
		int output = 0;
	} channels_[3];
	void update_channel(int);

	// Noise channel.
	struct Noise {
		// User-set values.
		uint8_t amplitude[2]{};
		enum class Frequency {
			DivideByFour,
			ToneChannel0,
			ToneChannel1,
			ToneChannel2,
		} frequency = Frequency::DivideByFour;
		enum class Polynomial {
			SeventeenBit,
			FifteenBit,
			ElevenBit,
			NineBit
		} polynomial = Polynomial::SeventeenBit;
		bool swap_polynomial = false;
		bool low_pass = false;
		bool high_pass = false;
		bool ring_modulate = false;

		// Current state.
		int count = 0;
		int output = 0;
		bool final_output = false;
	} noise_;
	void update_noise();

	bool use_direct_output_[2]{};

	// Global volume, per SampleSource obligations.
	int16_t volume_ = 0;

	// Polynomials that are always running.
	Numeric::LFSRv<0xc> poly4_;
	Numeric::LFSRv<0x14> poly5_;
	Numeric::LFSRv<0x60> poly7_;

	// The selectable, noise-related polynomial.
	Numeric::LFSRv<0x110> poly9_;
	Numeric::LFSRv<0x500> poly11_;
	Numeric::LFSRv<0x6000> poly15_;
	Numeric::LFSRv<0x12000> poly17_;

	// Current state of the active polynomials.
	uint8_t poly_state_[4];
};

/*!
	Provides Dave's timed interrupts — those that are provided at 1 kHz,
	50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
	1 Hz interrupt.
*/
class TimedInterruptSource {
public:
	/// Modifies an register in the audio range; only the low 4 bits are
	/// used for register decoding so it's assumed that the caller has
	/// already identified this write as being to an audio register.
	void write(uint16_t address, uint8_t value);

	/// Returns a bitmask of interrupts that have become active since
	/// the last time this method was called; flags are as defined in
	/// @c Enterprise::Dave::Interrupt
	uint8_t get_new_interrupts();

	/// Returns the current high or low states of the inputs that trigger
	/// the interrupts modelled here, as a bit mask compatible with that
	/// exposed by Dave as the register at 0xb4.
	uint8_t get_divider_state();

	/// Advances the interrupt source.
	void run_for(Cycles);

	/// @returns The amount of time from now until the earliest that
	/// @c get_new_interrupts() _might_ have new interrupts to report.
	Cycles next_sequence_point() const;

private:
	static constexpr Cycles clock_rate{250000};
	static constexpr Cycles half_clock_rate{125000};

	// Global divider (i.e. 8MHz/12Mhz switch).
	Cycles global_divider_ = Cycles(2);
	Cycles run_length_;

	// Interrupts that have fired since get_new_interrupts()
	// was last called.
	uint8_t interrupts_ = 0;

	// A counter for the 1Hz interrupt.
	int two_second_counter_ = 0;

	// A counter specific to the 1kHz and 50Hz timers, if in use.
	enum class InterruptRate {
		OnekHz,
		FiftyHz,
		ToneGenerator0,
		ToneGenerator1,
	} rate_ = InterruptRate::OnekHz;
	bool programmable_level_ = false;

	// A local duplicate of the counting state of the first two audio
	// channels, maintained in case either of those is used as an
	// interrupt source.
	struct Channel {
		int value = 100, reload = 100;
		bool sync = false;
		bool level = false;
	} channels_[2];
	void update_channel(int c, bool is_linked, int decrement);
};

}
